/*
 * Copyright (C) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "avcodec_video_encoder_demo.h"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <inttypes.h>
#include <multimedia/av_codec/native_avcodec_base.h>
#include <multimedia/av_codec/native_avformat.h>
#include <multimedia/av_codec/native_avmemory.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <sys/time.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avmemory.h>
#include "multimedia/player_framework/native_averrors.h"
#include <native_window/external_window.h>
#include <native_buffer/native_buffer.h>
#include <new>
#include "avcodec_log.h"


using namespace std;
namespace {
const int32_t DEFAULT_WIDTH = 640;
const int32_t DEFAULT_HEIGHT = 480;
const int32_t DEFAULT_FRAME_RATE = 20;
const int32_t FRAME_DURATION_US = 33000;
const int32_t SLEEP_TIME = 10;
uint32_t writeFrameCount = 0;
} // namespace
char* outputFilePath = "/data/storage/el2/base/haps/entry/files/out_file.h264";
char* inputFilePath = "/data/storage/el2/base/haps/entry/files/test.yuv";
std::mutex VEncDemo::mutex_;
std::map<OH_AVCodec *, AVCodecCallback*> VEncDemo::callBackMap_;
void I420ToNV21(unsigned char *pSrcData, uint16_t wWidth, uint16_t wHeight,unsigned char *pDstData[])
{
    uint32_t dwSize = wWidth * wHeight, i, j;
    uint8_t *pSrcData_U = pSrcData + dwSize;
    uint8_t *pSrcData_V = pSrcData + dwSize + (dwSize >> 2);
 
    memcpy(pDstData, pSrcData, dwSize);//y分量
 
    for(i = 0, j = 0; i < (dwSize >> 2); i++, j += 2)
    {
         memcpy(pDstData[1] + j + 1, pSrcData_U + i, 1);//u分量
         memcpy(pDstData[1] + j, pSrcData_V + i, 1);//v分量
    }
}

void VEncDemo::OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{
    AVCODEC_LOGE("OnError %d",errorCode);
    AVCodecCallback* callback = GetCallback(codec);
    if (callback != nullptr) {
        callback->OnCodecError(errorCode);
    }
}
void VEncDemo::OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
{
    AVCODEC_LOGD("%s format = %s ",__FUNCTION__,OH_AVFormat_DumpInfo(format));
    AVCodecCallback* callback = GetCallback(codec);
    if (callback != nullptr) {
        callback->OnOutputFormatChanged(format);
    }
}

void VEncDemo::OnNeedInputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, void *userData)
{
    AVCODEC_LOGD("%s received, index: %d",__FUNCTION__,index);
    AVCodecCallback* callback = GetCallback(codec);
    if (callback != nullptr) {
        callback->OnInputBufferAvailable(index,data);
    }
    
}

void VEncDemo::OnNewOutputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, OH_AVCodecBufferAttr *attr,
                                    void *userData)
{
    //AVCODEC_LOGD("%s received, index: %d,data = %d size = %" PRId32"\n" ,__FUNCTION__,index,data,attr->size);
    AVCodecCallback* callback = GetCallback(codec);
    if (callback != nullptr) {
        callback->OnOutputBufferAvailable(index,data,attr);
        OH_VideoEncoder_FreeOutputData(codec,index);
    }
}

void VEncDemo::RunCase()
{
    AVCODEC_LOGD("VEncDemo RunCase");
    DEMO_CHECK_AND_RETURN_LOG(CreateEnc() == AV_ERR_OK, "Fatal: CreateDec fail");
    AVCODEC_LOGD("VEncDemo CreateEnc ");
    OH_AVFormat *format = OH_AVFormat_Create();
    OH_AVFormat_SetStringValue(format,OH_MD_KEY_CODEC_MIME,OH_AVCODEC_MIMETYPE_VIDEO_AVC);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, DEFAULT_WIDTH);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, DEFAULT_HEIGHT);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, 1);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_FRAME_RATE, DEFAULT_FRAME_RATE);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_BITRATE,DEFAULT_WIDTH*DEFAULT_HEIGHT*DEFAULT_FRAME_RATE*0.2);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE,0);
    OH_AVFormat_SetIntValue(format, "codec_quality",0);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL,50);
    AVCODEC_LOGD("VEncDemo CreateFormat ");
    DEMO_CHECK_AND_RETURN_LOG(Configure(format) == AV_ERR_OK, "Fatal: Configure fail");
    AVCODEC_LOGD("VEncDemo ConfigureFormat ");
    DEMO_CHECK_AND_RETURN_LOG(Start() == AV_ERR_OK, "Fatal: Start fail");
    AVCODEC_LOGD("encoder start success");
}
void VEncDemo::StopCase(){
    DEMO_CHECK_AND_RETURN_LOG(Stop() == AV_ERR_OK, "Fatal: Stop fail");
    DEMO_CHECK_AND_RETURN_LOG(Release() == AV_ERR_OK, "Fatal: Release fail");
    AVCODEC_LOGD("encoder stop success");
}

VEncDemo::VEncDemo()
{
    isRunning_ = false;
    
    fout = fopen(outputFilePath,"ab+");
    if(fout == nullptr){
        AVCODEC_LOGE("fopen file %s fail: %s",outputFilePath,strerror(errno));
    } else {
        AVCODEC_LOGD("fopen file %s success",outputFilePath);
    }
}

VEncDemo::~VEncDemo()
{
    if (signal_) {
        delete signal_;
        signal_ = nullptr;
    }
    if(fout != nullptr){
        fclose(fout);
    }
}

int32_t VEncDemo::CreateEnc()
{
    videoEnc_ = OH_VideoEncoder_CreateByMime("video/avc");
    DEMO_CHECK_AND_RETURN_RET_LOG(videoEnc_ != nullptr, AV_ERR_UNKNOWN, "Fatal: CreateByName fail");

    signal_ = new VEncSignal();
    DEMO_CHECK_AND_RETURN_RET_LOG(signal_ != nullptr, AV_ERR_UNKNOWN, "Fatal: No memory");
    SetCallback(videoEnc_, this);
    struct OH_AVCodecAsyncCallback callback;
            callback.onError = VEncDemo::OnError;
            callback.onStreamChanged = VEncDemo::OnStreamChanged;
            callback.onNeedInputData = VEncDemo::OnNeedInputData;
            callback.onNeedOutputData = VEncDemo::OnNewOutputData;
    int32_t ret = OH_VideoEncoder_SetCallback(videoEnc_, callback, signal_);
    DEMO_CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AV_ERR_UNKNOWN, "Fatal: SetCallback fail");
    return AV_ERR_OK;
}

int32_t VEncDemo::Configure(OH_AVFormat *format)
{
    return OH_VideoEncoder_Configure(videoEnc_, format);
}

int32_t VEncDemo::GetSurface(OHNativeWindow *window)
{
    return OH_VideoEncoder_GetSurface(videoEnc_, &window);
}

int32_t VEncDemo::Start()
{
    //inputFile_ = std::make_unique<std::ifstream>();
    //DEMO_CHECK_AND_RETURN_RET_LOG(inputFile_ != nullptr, AV_ERR_UNKNOWN, "Fatal: No memory");
    //inputFile_->open(inputFilePath, std::ios::in | std::ios::binary);
    isRunning_.store(true);
    //AVCODEC_LOGD("Start isRunning_ == true");
    //inputLoop_ = make_unique<thread>(&VEncDemo::InputFunc, this);
    //DEMO_CHECK_AND_RETURN_RET_LOG(inputLoop_ != nullptr, AV_ERR_UNKNOWN, "Fatal: No memory");
    outFile_ = std::make_unique<std::ofstream>();
    //DEMO_CHECK_AND_RETURN_RET_LOG(outFile_ != nullptr, AV_ERR_UNKNOWN, "Fatal: No memory");
    outFile_ -> open(outputFilePath, std::ios::out | std::ios::binary);
    //outputLoop_ = make_unique<thread>(&VEncDemo::OutputFunc, this);
    //DEMO_CHECK_AND_RETURN_RET_LOG(outputLoop_ != nullptr, AV_ERR_UNKNOWN, "Fatal: No memory");
    int32_t ret;     
    //DEMO_CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AV_ERR_UNKNOWN, "Fatal: encoder getSurface fail");
    ret = OH_VideoEncoder_Prepare(videoEnc_);
    DEMO_CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AV_ERR_UNKNOWN, "Fatal: encoder prepare fail");
    return OH_VideoEncoder_Start(videoEnc_);
}

int32_t VEncDemo::Stop()
{
    isRunning_.store(false);
    AVCODEC_LOGD("Stop isRunning_ == false");
    if (inputLoop_ != nullptr && inputLoop_->joinable()) {
        unique_lock<mutex> lock(signal_->inMutex_);
        signal_->inCond_.notify_all();
        lock.unlock();
        inputLoop_->join();
    }

    if (outputLoop_ != nullptr && outputLoop_->joinable()) {
        unique_lock<mutex> lock(signal_->outMutex_);
        signal_->outCond_.notify_all();
        lock.unlock();
        outputLoop_->join();
    }
    if(inputFile_ != nullptr){
        inputFile_->close();
    }
    if (outFile_ != nullptr) {
        outFile_->close();
    }
    if(fout != nullptr){
        fclose(fout);
    }
    return OH_VideoEncoder_Stop(videoEnc_);
}

int32_t VEncDemo::Flush()
{
    return OH_VideoEncoder_Flush(videoEnc_);
}

int32_t VEncDemo::Reset()
{
    return OH_VideoEncoder_Reset(videoEnc_);
}

int32_t VEncDemo::Release()
{
    return OH_VideoEncoder_Destroy(videoEnc_);
}

int32_t VEncDemo::ExtractPacket()
{
        int32_t len = 0;
        int32_t ret = 0;
    
        if (data_ == nullptr) {
            data_ = inbuf_;
            (void)inputFile_->read(reinterpret_cast<char *>(inbuf_), VIDEO_INBUF_SIZE);
            data_size_ = inputFile_->gcount();
        }
    
        if ((data_size_ < VIDEO_REFILL_THRESH) && !file_end_) {
            memcpy(inbuf_, data_, data_size_);
            data_ = inbuf_;
            //(void)inputFile_->read(reinterpret_cast<char *>(data_ + data_size_), VIDEO_INBUF_SIZE - data_size_);
            //len = inputFile_->gcount();
            if (len > 0) {
                data_size_ += len;
            } else if (len == 0 && data_size_ == 0) {
                file_end_ = true;
            }
        }
    return 0;
}

void VEncDemo::InputFunc(){
    if(inputFile_->is_open()){
        size_t len = 0;
        inputFile_->read(reinterpret_cast<char *>(inbuf_), VIDEO_INBUF_SIZE);
        len = inputFile_->gcount();
        while(len >0){
            inputFile_->read(reinterpret_cast<char *>(inbuf_), VIDEO_INBUF_SIZE);
                    len = inputFile_->gcount();
        }
    }
}

void VEncDemo::sendDataPackage(unsigned char *data, int32_t len,int64_t timestamp){
    unique_lock<mutex> lock(signal_->inMutex_);
    if(signal_->inQueue_.size() <= 0){
        AVCODEC_LOGE("inQueue_ is empty");
        return;
    }
    uint32_t index = signal_->inQueue_.front();
    auto buffer = signal_->inBufferQueue_.front();
    OH_AVCodecBufferAttr info;
    if(len == 0){
        info.size = 0;
        info.offset = 0;
        info.pts = 0;
        info.flags = AVCODEC_BUFFER_FLAGS_EOS;
        OH_VideoEncoder_PushInputData(videoEnc_, index, info);
        AVCODEC_LOGE("send data len == 0");
    }
    info.size = len;
    info.offset = 0;
    info.pts = timestamp;
    memcpy(OH_AVMemory_GetAddr(buffer), data, OH_AVMemory_GetSize(buffer));
     int32_t ret = AV_ERR_OK;
    AVCODEC_LOGD("OH_VideoEncoder_PushInputData");
    if (isFirstFrame_) {
        info.flags = AVCODEC_BUFFER_FLAGS_CODEC_DATA;
        ret = OH_VideoEncoder_PushInputData(videoEnc_, index, info);
        isFirstFrame_ = false;
    } else {
        info.flags = AVCODEC_BUFFER_FLAGS_NONE;
        ret = OH_VideoEncoder_PushInputData(videoEnc_, index, info);
    }
    if (ret != AV_ERR_OK) {
        AVCODEC_LOGE("Fatal error, send data exit");
        return;
    }
    signal_->inQueue_.pop();
    signal_->inBufferQueue_.pop();
}

void VEncDemo::OutputFunc(uint32_t index,OH_AVMemory* data,OH_AVCodecBufferAttr* attr)
{
    AVCODEC_LOGD("%s",__FUNCTION__);
    if (outFile_ != nullptr && attr->size != 0 && data != nullptr && OH_AVMemory_GetAddr(data) != nullptr) {
        //cout << "OutputFunc write file,buffer index" << index << ", data size = :" << attr.size << endl;
        AVCODEC_LOGD("OutputFunc write file %d",OH_AVMemory_GetAddr(data));
        //outFile_->write(reinterpret_cast<char *>(OH_AVMemory_GetAddr(data)), attr->size);
        fwrite(reinterpret_cast<char *>(OH_AVMemory_GetAddr(data)),attr->size,1,fout);
        writeFrameCount++;
    }

    if (attr->flags == AVCODEC_BUFFER_FLAGS_EOS) {
        AVCODEC_LOGD("encode eos, write frame:%d",writeFrameCount);
        isRunning_.store(false);
        AVCODEC_LOGD("OutputFunc isRunning_ == false");
    }
}

void VEncDemo::OnCodecError(int32_t errorCode){
     AVCODEC_LOGD("%s",__FUNCTION__);
    
}
void VEncDemo::OnInputBufferAvailable(uint32_t index, OH_AVMemory* data){
    AVCODEC_LOGD("%s enter",__FUNCTION__);
    unique_lock<mutex> lock(signal_->inMutex_);
    signal_->inQueue_.push(index);
    signal_->inBufferQueue_.push(data);
    
    AVCODEC_LOGD("%s exit inQueue_.size = %d",__FUNCTION__,signal_->inQueue_.size());
}
void VEncDemo::OnOutputBufferAvailable(uint32_t index, OH_AVMemory* data, OH_AVCodecBufferAttr* attr){
     AVCODEC_LOGD("%s",__FUNCTION__);
     OutputFunc(index,data, attr);
}
void VEncDemo::OnOutputFormatChanged(OH_AVFormat* format){
     AVCODEC_LOGD("%s",__FUNCTION__);
    
}
void VEncDemo::SetCallback(OH_AVCodec *codec, AVCodecCallback* cb){
     AVCODEC_LOGD("%s",__FUNCTION__);
    std::lock_guard<std::mutex> lock(mutex_);
    callBackMap_[codec] = cb;
}
void VEncDemo::DelCallback(OH_AVCodec *codec){
     AVCODEC_LOGD("%s",__FUNCTION__);
    auto it = callBackMap_.find(codec);
    if (it != callBackMap_.end()) {
        callBackMap_.erase(it);
    }
}
AVCodecCallback* VEncDemo::GetCallback(OH_AVCodec *codec){
     AVCODEC_LOGD("%s",__FUNCTION__);
    std::lock_guard<std::mutex> lock(mutex_);
    if (callBackMap_.find(codec) != callBackMap_.end()) {
        return callBackMap_.at(codec);
    }
    return nullptr;
}